package com.abou.utils.common;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.log4j.Logger;

/**
 * Utility methods for viewing java beans via reflexion. Use
 * <strong>org.apache.commons.beanutils.BeanUtils</strong> for any property
 * extraction or transfer methods.
 * <p>
 * Caution : show only declared fields! Inherited fields will not appear.
 * </p>
 */
public final class BeanUtils {
  
  /**
   * The prefix of the name of a logger field.
   */
  private static final String LOG_FIELD_NAME = "LOGGER";
  
  private static final Logger LOGGER = Logger.getLogger(BeanUtils.class);
  
  /**
   * The private constructor used to prevent the class from being instantiated.
   */
  private BeanUtils() {

    super();
  }
  
  /**
   * Returns a string representation of the association between the key and the
   * value.
   * 
   * @param key A key string.
   * @param value A value.
   * @return A string representation of the association between the key and the
   *         value.
   */
  private static String formatKeyAndValue(String key, Object value) {

    return "[" + key + " = " + value + "] ";
  }
  
  /**
   * Returns the string representation of all the values (string and other
   * object values) of the non-static properties of the passed object that is
   * supposed to bean an instance of a java bean.
   * 
   * @param obj An object.
   * @param inDeep Whether to print the attributes values of the objects
   *          referenced by the passed object, or not.
   * @return The string representation of the data of the object.
   */
  public static String toStringAllProperties(Object obj, boolean inDeep) {

    String repr = null;
    
    if (obj != null) {
      
      StringBuffer str = new StringBuffer();
      str = str.append(obj.getClass().getSimpleName() + " : ");
      
      try {
        
        List<Field> fields = new ArrayList<Field>();
        List<Field> classFields = null;
        Class<?> curClass = obj.getClass();
        
        do {
          classFields =
              new ArrayList<Field>(Arrays.asList(curClass.getDeclaredFields()));
          Collections.reverse(classFields);
          fields.addAll(classFields);
          curClass = curClass.getSuperclass();
        } while (curClass != null && !curClass.isInterface());
        
        Collections.reverse(fields);
        
        for (Field field : fields) {
          
          field.setAccessible(true);
          Object value = field.get(obj);
          
          if (!Modifier.isStatic(field.getModifiers())
              && !field.getName().equals(LOG_FIELD_NAME)) {
            
            if (value instanceof String || !inDeep) {
              
              str.append(formatKeyAndValue(field.getName(), value));
              
            } else {
              
              str.append(formatKeyAndValue(field.getName(),
                  toStringAllProperties(value, true)));
            }
          }
        }
      } catch (IllegalAccessException ex) {
        
        LOGGER.error(ex.getMessage(), ex);
      }
      
      if (str.length() > 0) {
        
        repr = str.toString();
      }
    }
    
    return repr;
  }
  
  /**
   * Retrieves the parameterized type defined in the super class of the passed
   * class.
   * 
   * @param theClass A parameterized class
   * @param index The index of the parameter
   * @return The parameter class
   */
  public static <T> Class<T> getParameterizedTypeFromSuperClass(
      Class<?> theClass, int index) {

    // getting the parameterized type for this class
    Class<?> curClass = theClass;
    ParameterizedType paramType = null;
    
    do {
      
      if (curClass.getGenericSuperclass() instanceof ParameterizedType) {
        
        paramType = (ParameterizedType) curClass.getGenericSuperclass();
        
      } else if (curClass.getGenericInterfaces().length > 0
          && curClass.getGenericInterfaces()[0] instanceof ParameterizedType) {
        
        paramType = (ParameterizedType) curClass.getGenericInterfaces()[0];
        
      } else {
        
        curClass = curClass.getSuperclass();
      }
      
    } while (paramType == null && curClass != null);
    
    // getting the class for the parameterized type
    Class<T> retClass = null;
    int theIndex = index;
    Type[] paramTypes = paramType.getActualTypeArguments();
    
    if (paramTypes != null) {
      
      if ((theIndex < 0 || theIndex >= paramTypes.length)
          && paramTypes.length > 0) {
        
        theIndex = paramTypes.length - 1;
      }
      
      retClass = getClass(paramTypes[theIndex]);
    }
    
    return retClass;
  }
  
  /**
   * Get the underlying class for a type, or null if the type is a variable
   * type.
   * 
   * @param <T> The type for the searched class
   * @param type The type
   * @return The underlying class
   */
  @SuppressWarnings("unchecked")
  public static <T> Class<T> getClass(Type type) {

    Class<T> theClass = null;
    
    if (Class.class.equals(type.getClass())) {
      
      theClass = (Class<T>) type;
      
    } else if (type instanceof ParameterizedType) {
      
      theClass = getClass(((ParameterizedType) type).getRawType());
      
    } else if (type instanceof GenericArrayType) {
      
      Type componentType = ((GenericArrayType) type).getGenericComponentType();
      Class<?> componentClass = getClass(componentType);
      
      if (componentClass != null) {
        
        theClass = (Class<T>) Array.newInstance(componentClass, 0).getClass();
      }
    }
    
    return theClass;
  }
  
  /**
   * Returns a new array that is a clone of the passed one.
   * 
   * @param <T> The type of the array elements
   * @param array The array to clone
   * @return The new array
   */
  @SuppressWarnings("unchecked")
  public static <T> T[] clone(T[] array) {

    T[] cloneArray = null;
    
    if (array != null) {
      
      // getting the T type
      Class<?> tType = array.getClass().getComponentType();
      
      // creating an array of the T type
      cloneArray = (T[]) Array.newInstance(tType, array.length);
      
      // copying the array elements
      System.arraycopy(array, 0, cloneArray, 0, array.length);
    }
    
    return cloneArray;
  }
}
